#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Java String字段扫描工具
用于统计Java源码中字段类型为String，且字段名中带有name、caption等关键词的字段
"""
import os
import re
import argparse
import csv
from concurrent.futures import ThreadPoolExecutor
from tqdm import tqdm

# 字段名中需要包含的关键词
TARGET_KEYWORDS = ['name', 'caption', 'title', 'label', 'desc', 'description', 'text', 'message', 'content']

# 排除的目录
EXCLUDE_DIRS = {'.git', '__pycache__', 'node_modules', 'vendor', 'dist', 'build', 'target', 'logs', 'tmp', 'temp', '.idea', '.vscode', '.svn'}

class JavaStringFieldScanner:
    def __init__(self, root_dir, output_file, max_workers=4, custom_keywords=None):
        self.root_dir = root_dir
        self.output_file = output_file
        self.max_workers = max_workers
        self.keywords = TARGET_KEYWORDS.copy()
        if custom_keywords:
            self.keywords.extend(custom_keywords)
        self.results = []
        self.total_files = 0
        self.scanned_files = 0
        
    def is_java_file(self, filename):
        """检查文件是否为Java源文件"""
        _, ext = os.path.splitext(filename)
        return ext.lower() == '.java'
    
    def is_excluded(self, path):
        """检查路径是否需要排除"""
        for dir_name in EXCLUDE_DIRS:
            if f"{os.sep}{dir_name}{os.sep}" in path or path.endswith(f"{os.sep}{dir_name}"):
                return True
        return False
    
    def scan_file(self, file_path):
        """扫描单个Java文件中的String字段"""
        if not self.is_java_file(os.path.basename(file_path)):
            return
        
        try:
            with open(file_path, 'r', encoding='utf-8', errors='replace') as f:
                content = f.read()
            
            # 获取相对路径
            rel_path = os.path.relpath(file_path, self.root_dir)
            
            # 提取类名
            class_name = "UnknownClass"
            class_match = re.search(r'\bclass\s+(\w+)', content)
            if class_match:
                class_name = class_match.group(1)
            
            # 提取字段注释和字段定义
            # 处理JavaDoc注释
            javadoc_comments = {}
            
            # 首先提取所有的JavaDoc注释和紧接着的字段定义
            # 查找所有JavaDoc注释
            javadoc_matches = re.finditer(r'(/\*\*[\s\S]*?\*/)', content)
            
            for javadoc_match in javadoc_matches:
                comment = javadoc_match.group(1)
                # 获取注释后的内容，查找下一个String字段定义
                remaining_content = content[javadoc_match.end():]
                field_match = re.search(r'\s*(private|public|protected|\s)*\s*(?:java\.lang\.)?String\s+(\w+)', remaining_content)
                
                if field_match:
                    field_name = field_match.group(2)
                    # 清理注释格式
                    clean_comment = re.sub(r'^\s*\*\s?', '', comment, flags=re.MULTILINE)
                    clean_comment = clean_comment.replace('/**', '').replace('*/', '').strip()
                    # 移除多余的空行
                    clean_comment = '\n'.join([line for line in clean_comment.split('\n') if line.strip()])
                    # 替换可能残留的斜杠和引号
                    clean_comment = clean_comment.replace('"', '""').replace('/', '')
                    # 移除末尾的换行符
                    if clean_comment.endswith('\n'):
                        clean_comment = clean_comment[:-1]
                    javadoc_comments[field_name] = clean_comment
            
            # 匹配字段定义（包括public, private, protected修饰符或无修饰符）
            # 支持String、java.lang.String等格式
            # 支持带泛型的情况（如List<String>）
            field_pattern = re.compile(r'(?:private|public|protected|\s)*\s*(?:java\.lang\.)?String\s+(\w+)(?:<[^>]*>)?\s*[;=]', re.MULTILINE)
            
            for match in field_pattern.finditer(content):
                field_name = match.group(1)
                
                # 检查字段名是否包含目标关键词
                contains_keyword = any(keyword.lower() in field_name.lower() for keyword in self.keywords)
                
                if contains_keyword:
                    # 获取字段所在行号
                    lines = content[:match.start()].split('\n')
                    line_number = len(lines)
                    
                    # 获取字段注释
                    comment = javadoc_comments.get(field_name, "无注释")
                    
                    # 添加到结果
                    self.results.append({
                        'file_path': rel_path,
                        'class_name': class_name,
                        'field_name': field_name,
                        'line_number': line_number,
                        'comment': comment
                    })
            
        except Exception as e:
            print(f"扫描文件时出错 {file_path}: {e}")
        finally:
            self.scanned_files += 1
    
    def count_files(self):
        """统计需要扫描的Java文件总数"""
        count = 0
        for root, _, files in os.walk(self.root_dir):
            if self.is_excluded(root):
                continue
            
            for file in files:
                if self.is_java_file(file):
                    count += 1
        
        self.total_files = count
        return count
    
    def scan(self):
        """开始扫描"""
        print(f"开始扫描Java源码: {self.root_dir}")
        print(f"正在统计需要扫描的Java文件数量...")
        self.count_files()
        print(f"总共需要扫描 {self.total_files} 个Java文件")
        
        # 创建任务列表
        file_paths = []
        for root, _, files in os.walk(self.root_dir):
            if self.is_excluded(root):
                continue
            
            for file in files:
                if self.is_java_file(file):
                    file_paths.append(os.path.join(root, file))
        
        # 使用线程池进行并行扫描
        with ThreadPoolExecutor(max_workers=self.max_workers) as executor:
            # 使用tqdm显示进度
            list(tqdm(executor.map(self.scan_file, file_paths), total=len(file_paths)))
        
        # 保存结果
        self.save_results()
        
        print(f"扫描完成！共发现 {len(self.results)} 个符合条件的String字段")
        print(f"结果已保存到: {self.output_file}")
        
        # 输出汇总统计
        self.print_summary()
    
    def save_results(self):
        """保存扫描结果到CSV文件"""
        if not self.results:
            print("未发现符合条件的String字段")
            return
        
        # 确保输出目录存在
        output_dir = os.path.dirname(self.output_file)
        if output_dir and not os.path.exists(output_dir):
            os.makedirs(output_dir)
        
        # 写入CSV文件
        with open(self.output_file, 'w', newline='', encoding='utf-8-sig') as f:
            writer = csv.DictWriter(f, fieldnames=['file_path', 'class_name', 'field_name', 'line_number', 'comment'])
            writer.writeheader()
            writer.writerows(self.results)
    
    def print_summary(self):
        """打印汇总统计信息"""
        if not self.results:
            return
        
        print("\n=== 汇总统计 ===")
        
        # 按关键词统计
        keyword_stats = {keyword: 0 for keyword in self.keywords}
        for result in self.results:
            field_name = result['field_name'].lower()
            for keyword in self.keywords:
                if keyword.lower() in field_name:
                    keyword_stats[keyword] += 1
        
        # 按类统计
        class_stats = {}
        for result in self.results:
            class_name = result['class_name']
            if class_name not in class_stats:
                class_stats[class_name] = 0
            class_stats[class_name] += 1
        
        # 输出关键词统计
        print("\n按关键词分布:")
        for keyword, count in sorted(keyword_stats.items(), key=lambda x: x[1], reverse=True):
            if count > 0:
                print(f"  {keyword}: {count}个字段")
        
        # 输出类统计（只显示前10个）
        print("\n按类分布（前10个）:")
        for i, (class_name, count) in enumerate(sorted(class_stats.items(), key=lambda x: x[1], reverse=True)):
            if i < 10:
                print(f"  {class_name}: {count}个字段")
            else:
                break

if __name__ == "__main__":
    # 命令行参数解析
    parser = argparse.ArgumentParser(description='Java String字段扫描工具')
    parser.add_argument('-d', '--dir', type=str, default='.', help='要扫描的根目录')
    parser.add_argument('-o', '--output', type=str, default='java_string_fields.csv', help='输出的CSV文件路径')
    parser.add_argument('-w', '--workers', type=int, default=4, help='并行工作线程数')
    parser.add_argument('-k', '--keywords', type=str, nargs='*', default=[], help='额外的关键词')
    
    args = parser.parse_args()
    
    # 创建扫描器并开始扫描
    scanner = JavaStringFieldScanner(
        root_dir=os.path.abspath(args.dir),
        output_file=os.path.abspath(args.output),
        max_workers=args.workers,
        custom_keywords=args.keywords
    )
    
    scanner.scan()